﻿namespace WgFrame.Net.Protocols
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Text;
    using WgFrame.Utils;
    using WgFrame.Utils;

    public enum GpType : byte
    {
        Unknown = 0,
        Boolean = 1,
        BooleanArray = 2,
        Byte = 3,
        ByteArray = 4,
        Short = 5,
        ShortArray = 6,
        Float = 7,
        FloatArray = 8,
        Double = 9,
        DoubleArray = 10,
        Integer = 11,
        IntegerArray = 12,
        Long = 13,
        LongArray = 14,
        String = 15,
        StringArray = 16,
        Hashtable = 17,
        Null = 18,
        OperationRequest = 19,
        OperationResponse = 20,
        HashtableArray = 21
    }

    public class Protocol16 : IProtocol
    {
        private readonly byte[] versionBytes = new byte[] { 1, 6 };
        private readonly byte[] memShort = new byte[2];
        private readonly long[] memLongBlock = new long[1];
        private readonly byte[] memLongBlockBytes = new byte[8];
        private static readonly float[] memFloatBlock = new float[1];
        private static readonly byte[] memFloatBlockBytes = new byte[4];
        private readonly double[] memDoubleBlock = new double[1];
        private readonly byte[] memDoubleBlockBytes = new byte[8];
        private readonly byte[] memInteger = new byte[4];
        private readonly byte[] memLong = new byte[8];
        private readonly byte[] memFloat = new byte[4];
        private readonly byte[] memDouble = new byte[8];
        private byte[] memString;

        public override string ProtocolType
        {
            get
            {
                return "GpBinaryV16";
            }
        }

        public override byte[] VersionBytes
        {
            get
            {
                return versionBytes;
            }
        }

        public override OperationRequest DeserializeOperationRequest(StreamBuffer din)
        {
            return new OperationRequest
            {
                OperationCode = DeserializeShort(din),
                Parameters = DeserializeParameterTable(din, null)
            };
        }

        public override OperationResponse DeserializeOperationResponse(StreamBuffer stream)
        {
            OperationResponse response = new OperationResponse();
            response.OperationCode = DeserializeShort(stream);
            response.ReturnCode = DeserializeShort(stream);
            response.DebugMessage = Deserialize(stream, DeserializeByte(stream)) as string;
            response.Parameters = DeserializeParameterTable(stream, null);
            return response;
        }

        public override void SerializeOperationRequest(StreamBuffer stream, short operationCode, Dictionary<byte, object> parameters, bool setType)
        {
            if (setType)
            {
                stream.WriteByte((byte)GpType.OperationRequest);
            }
            stream.Write(GBitConverter.GetBytes(operationCode), 0, 2);
            SerializeParameterTable(stream, parameters);
        }

        public override void SerializeOperationResponse(StreamBuffer stream, OperationResponse serObject, bool setType)
        {
            if (setType)
            {
                stream.WriteByte((byte)GpType.OperationResponse);
            }
            stream.Write(GBitConverter.GetBytes(serObject.OperationCode), 0, 2);
            SerializeShort(stream, serObject.ReturnCode, false);
            if (string.IsNullOrEmpty(serObject.DebugMessage))
            {
                stream.WriteByte((byte)(GpType.Null));
            }
            else
            {
                SerializeString(stream, serObject.DebugMessage, false);
            }
            SerializeParameterTable(stream, serObject.Parameters);
        }

        private object Deserialize(StreamBuffer din, byte type)
        {
            object obj2;
            switch ((GpType)type)
            {
                case GpType.Unknown:
                case GpType.Null:
                    obj2 = null;
                    break;

                case GpType.Boolean:
                    obj2 = DeserializeBoolean(din);
                    break;

                case GpType.BooleanArray:
                    obj2 = DeserializeBooleanArray(din);
                    break;

                case GpType.Byte:
                    obj2 = DeserializeByte(din);
                    break;

                case GpType.ByteArray:
                    obj2 = DeserializeByteArray(din);
                    break;

                case GpType.Short:
                    obj2 = DeserializeShort(din);
                    break;

                case GpType.ShortArray:
                    obj2 = DeserializeShortArray(din);
                    break;

                case GpType.Float:
                    obj2 = DeserializeFloat(din);
                    break;

                case GpType.FloatArray:
                    obj2 = DeserializeFloatArray(din);
                    break;

                case GpType.Double:
                    obj2 = DeserializeDouble(din);
                    break;

                case GpType.DoubleArray:
                    obj2 = DeserializeDoubleArray(din);
                    break;

                case GpType.Integer:
                    obj2 = DeserializeInteger(din);
                    break;

                case GpType.IntegerArray:
                    obj2 = DeserializeIntArray(din);
                    break;

                case GpType.Long:
                    obj2 = DeserializeLong(din);
                    break;

                case GpType.LongArray:
                    obj2 = DeserializeLongArray(din);
                    break;

                case GpType.String:
                    obj2 = DeserializeString(din);
                    break;

                case GpType.StringArray:
                    obj2 = DeserializeStringArray(din);
                    break;

                case GpType.Hashtable:
                    obj2 = DeserializeHashTable(din);
                    break;

                case GpType.HashtableArray:
                    obj2 = DeserializeHashtableArray(din);
                    break;

                case GpType.OperationResponse:
                    obj2 = DeserializeOperationResponse(din);
                    break;

                case GpType.OperationRequest:
                    obj2 = DeserializeOperationRequest(din);
                    break;

                default:
                    throw new Exception("Deserialize(): " + type + " pos: " + din.Position + " bytes: " + din.Length + ". " + SupportClass.ByteArrayToString(din.GetBuffer()));
            }
            return obj2;
        }

        private short DeserializeArrayLength(StreamBuffer din)
        {
            return DeserializeShort(din);
        }

        private bool DeserializeBoolean(StreamBuffer din)
        {
            return din.ReadByte() != 0;
        }

        private object DeserializeBooleanArray(StreamBuffer din)
        {
            int length = DeserializeArrayLength(din);
            bool[] values = new bool[length];
            for (int i=0; i<length; i++)
            {
                values[i] = DeserializeBoolean(din);
            }
            return values;
        }

        private byte DeserializeByte(StreamBuffer din)
        {
            return din.ReadByte();
        }
            

        private byte[] DeserializeByteArray(StreamBuffer din)
        {
            int size = DeserializeArrayLength(din);
            byte[] buffer = new byte[size];
            din.Read(buffer, 0, size);
            return buffer;
        }

        private short DeserializeShort(StreamBuffer din)
        {
            lock (memShort)
            {
                byte[] memShort = this.memShort;
                din.Read(memShort, 0, 2);
                return GBitConverter.ToShort(memShort);
            }
        }

        private object DeserializeShortArray(StreamBuffer din)
        {
            int length = DeserializeArrayLength(din);
            short[] values = new short[length];
            for(int i=0; i<length; i++)
            {
                values[i] = DeserializeShort(din);
            }
            return values;
        }

        private float DeserializeFloat(StreamBuffer din)
        {
            lock (memFloat)
            {
                byte[] memFloat = this.memFloat;
                din.Read(memFloat, 0, 4);
                return GBitConverter.ToFloat(memFloat);
            }
        }

        private object DeserializeFloatArray(StreamBuffer din)
        {
            int length = DeserializeArrayLength(din);
            float[] values = new float[length];
            for (int i = 0; i < length; i++)
            {
                values[i] = DeserializeFloat(din);
            }
            return values;
        }


        private double DeserializeDouble(StreamBuffer din)
        {
            lock (memDouble)
            {
                byte[] memDouble = this.memDouble;
                din.Read(memDouble, 0, 8);
                return GBitConverter.ToDouble(memDouble);
            }
        }

        private object DeserializeDoubleArray(StreamBuffer din)
        {
            int length = DeserializeArrayLength(din);
            double[] values = new double[length];
            for (int i = 0; i < length; i++)
            {
                values[i] = DeserializeDouble(din);
            }
            return values;
        }

        private int DeserializeInteger(StreamBuffer din)
        {
            lock (memInteger)
            {
                byte[] memInteger = this.memInteger;
                din.Read(memInteger, 0, 4);
                return GBitConverter.ToInt(memInteger);
            }
        }

        private int[] DeserializeIntArray(StreamBuffer din)
        {
            int length = DeserializeArrayLength(din);
            int[] values = new int[length];
            for (int i = 0; i < length; i++)
            {
                values[i] = DeserializeInteger(din);
            }
            return values;
        }

        private long DeserializeLong(StreamBuffer din)
        {
            lock (memLong)
            {
                byte[] memLong = this.memLong;
                din.Read(memLong, 0, 8);
                return GBitConverter.ToLong(memLong);
            }
        }

        private object DeserializeLongArray(StreamBuffer din)
        {
            int length = DeserializeArrayLength(din);
            long[] values = new long[length];
            for (int i = 0; i < length; i++)
            {
                values[i] = DeserializeLong(din);
            }
            return values;
        }

        private string DeserializeString(StreamBuffer din)
        {
            string str;
            short count = DeserializeShort(din);
            if (count == 0)
            {
                str = string.Empty;
            }
            else
            {
                if ((memString == null) || (memString.Length < count))
                {
                    memString = new byte[count];
                }
                din.Read(memString, 0, count);
                str = GBitConverter.ToString(memString, 0, count);
            }
            return str;
        }

        private string[] DeserializeStringArray(StreamBuffer din)
        {
            int length = DeserializeArrayLength(din);
            string[] strArray = new string[length];
            for (int i = 0; i < length; i++)
            {
                strArray[i] = DeserializeString(din);
            }
            return strArray;
        }

        private Hashtable DeserializeHashTable(StreamBuffer din)
        {
            int x = DeserializeShort(din);
            Hashtable hashtable = new Hashtable(x);
            for (int i = 0; i < x; i++)
            {
                object obj2 = Deserialize(din, din.ReadByte());
                object obj3 = Deserialize(din, din.ReadByte());
                if (obj2 != null)
                {
                    hashtable[obj2] = obj3;
                }
            }
            return hashtable;
        }

        private object DeserializeHashtableArray(StreamBuffer din)
        {
            int length = DeserializeArrayLength(din);
            Hashtable[] values = new Hashtable[length];
            for (int i = 0; i < length; i++)
            {
                values[i] = DeserializeHashTable(din);
            }
            return values;
        }

        private Dictionary<byte, object> DeserializeParameterTable(StreamBuffer stream, Dictionary<byte, object> target = null)
        {
            short capacity = DeserializeShort(stream);
            Dictionary<byte, object> dictionary = (target != null) ? target : new Dictionary<byte, object>(capacity);
            for (int i = 0; i < capacity; i++)
            {
                byte num2 = stream.ReadByte();
                object obj2 = Deserialize(stream, stream.ReadByte());
                dictionary[num2] = obj2;
            }
            return dictionary;
        }


        private GpType GetCodeOfType(Type type)
        {
            if (type == typeof(bool))
            {
                return GpType.Boolean;
            }
            else if (type == typeof(bool[]))
            {
                return GpType.BooleanArray;
            }
            else if (type == typeof(byte))
            {
                return GpType.Byte;
            }
            else if (type == typeof(byte[]))
            {
                return GpType.ByteArray;
            }
            else if (type == typeof(short))
            {
                return GpType.Short;
            }
            else if (type == typeof(short[]))
            {
                return GpType.ShortArray;
            }
            else if (type == typeof(float))
            {
                return GpType.Float;
            }
            else if (type == typeof(float[]))
            {
                return GpType.FloatArray;
            }
            else if (type == typeof(double))
            {
                return GpType.Double;
            }
            else if (type == typeof(double[]))
            {
                return GpType.DoubleArray;
            }
            else if (type == typeof(int))
            {
                return GpType.Integer;
            }
            else if (type == typeof(int[]))
            {
                return GpType.IntegerArray;
            }
            else if (type == typeof(long))
            {
                return GpType.Long;
            }
            else if (type == typeof(long[]))
            {
                return GpType.LongArray;
            }
            else if (type == typeof(string))
            {
                return GpType.String;
            }
            else if (type == typeof(string[]))
            {
                return GpType.StringArray;
            }
            else if (type == typeof(Hashtable))
            {
                return GpType.Hashtable;
            }
            else if (type == typeof(Hashtable[]))
            {
                return GpType.HashtableArray;
            }
            else if (type == typeof(OperationRequest))
            {
                return GpType.OperationRequest;
            }
            else if (type == typeof(OperationResponse))
            {
                return GpType.OperationResponse;
            }
            else
            {
                Debug.WriteLine("missing type: " + type.ToString());
                throw new Exception("serialize(): " + type.ToString());
            }
        }


        private void Serialize(StreamBuffer dout, object serObject, bool setType)
        {
            if (serObject == null)
            {
                if (setType)
                {
                    dout.WriteByte((byte)GpType.Null);
                }
            }
            else
            {
                GpType codeOfType = GetCodeOfType(serObject.GetType());
                switch (codeOfType)
                {
                    case GpType.Boolean:
                        SerializeBoolean(dout, (bool)serObject, setType);
                        break;

                    case GpType.BooleanArray:
                        SerializeBooleanArray(dout, (bool[])serObject, setType);
                        break;

                    case GpType.Byte:
                        SerializeByte(dout, (byte)serObject, setType);
                        break;

                    case GpType.ByteArray:
                        SerializeByteArray(dout, (byte[])serObject, setType);
                        break;

                    case GpType.Short:
                        SerializeShort(dout, (short)serObject, setType);
                        break;

                    case GpType.ShortArray:
                        SerializeShortArray(dout, (short[])serObject, setType);
                        break;

                    case GpType.Float:
                        SerializeFloat(dout, (float)serObject, setType);
                        break;

                    case GpType.FloatArray:
                        SerializeFloatArray(dout, (float[])serObject, setType);
                        break;

                    case GpType.Double:
                        SerializeDouble(dout, (double)serObject, setType);
                        break;

                    case GpType.DoubleArray:
                        SerializeDoubleArray(dout, (double[])serObject, setType);
                        break;

                    case GpType.Integer:
                        SerializeInteger(dout, (int)serObject, setType);
                        break;

                    case GpType.IntegerArray:
                        SerializeIntegerArray(dout, (int[])serObject, setType);
                        break;

                    case GpType.Long:
                        SerializeLong(dout, (long)serObject, setType);
                        break;

                    case GpType.LongArray:
                        SerializeLongArray(dout, (long[])serObject, setType);
                        break;

                    case GpType.String:
                        SerializeString(dout, (string)serObject, setType);
                        break;

                    case GpType.StringArray:
                        SerializeStringArray(dout, (string[])serObject, setType);
                        break;

                    case GpType.Hashtable:
                        SerializeHashTable(dout, (Hashtable)serObject, setType);
                        break;

                    case GpType.HashtableArray:
                        SerializeHashTableArray(dout, (Hashtable[])serObject, setType);
                        break;

                    case GpType.OperationResponse:
                        SerializeOperationResponse(dout, (OperationResponse)serObject, setType);
                        break;

                    case GpType.OperationRequest:
                        SerializeOperationRequest(dout, (OperationRequest)serObject, setType);
                        break;

                    default:
                        if (serObject is ArraySegment<byte>)
                        {
                            ArraySegment<byte> segment = (ArraySegment<byte>)serObject;
                            SerializeByteArraySegment(dout, segment.Array, segment.Offset, segment.Count, setType);
                        }
                        else
                        {
                            throw new Exception("cannot serialize(): " + codeOfType);
                        }
                        break;
                }
            }
        }

        private void SerializeArrayLength(StreamBuffer dout, int length)
        {
            SerializeShort(dout, (short)length, false);
        }

        private void SerializeBoolean(StreamBuffer dout, bool serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.Boolean);
            }
            dout.WriteByte(serObject ? ((byte) 1) : ((byte) 0));
        }

        private void SerializeBooleanArray(StreamBuffer dout, bool[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.BooleanArray);
            }

            SerializeArrayLength(dout, serObject.Length);
            for(int i=0; i<serObject.Length; i++)
            {
                SerializeBoolean(dout, serObject[i], false);
            }
        }

        private void SerializeByte(StreamBuffer dout, byte serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.Byte);
            }
            dout.WriteByte(serObject);
        }

        private void SerializeByteArray(StreamBuffer dout, byte[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.ByteArray);
            }
            SerializeArrayLength(dout, serObject.Length);
            dout.Write(serObject, 0, serObject.Length);
        }

        private void SerializeByteArraySegment(StreamBuffer dout, byte[] serObject, int offset, int count, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.ByteArray);
            }
            SerializeArrayLength(dout, count);
            dout.Write(serObject, offset, count);
        }

        private void SerializeShort(StreamBuffer dout, short serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.Short);
            }
            lock (memShort)
            {
                byte[] memShort = GBitConverter.GetBytes(serObject);
                dout.Write(memShort, 0, 2);
            }
        }

        private void SerializeShortArray(StreamBuffer dout, short[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.ShortArray);
            }
            SerializeArrayLength(dout, serObject.Length);
            for (int i = 0; i < serObject.Length; i++)
            {
                SerializeShort(dout, serObject[i], false);
            }
        }

        private void SerializeFloat(StreamBuffer dout, float serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.Float);
            }
            lock (memFloatBlockBytes)
            {
                memFloatBlock[0] = serObject;
                byte[] data = GBitConverter.GetBytes(serObject);
                dout.Write(data, 0, 4);
            }
        }

        private void SerializeFloatArray(StreamBuffer dout, float[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.FloatArray);
            }
            SerializeArrayLength(dout, serObject.Length);
            for (int i = 0; i < serObject.Length; i++)
            {
                SerializeFloat(dout, serObject[i], false);
            }
        }

        private void SerializeDouble(StreamBuffer dout, double serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.Double);
            }
            lock (memDoubleBlockBytes)
            {
                memDoubleBlock[0] = serObject;
                byte[] data = GBitConverter.GetBytes(serObject);
                dout.Write(data, 0, 8);
            }
        }

        private void SerializeDoubleArray(StreamBuffer dout, double[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.DoubleArray);
            }
            SerializeArrayLength(dout, serObject.Length);
            for (int i = 0; i < serObject.Length; i++)
            {
                SerializeDouble(dout, serObject[i], false);
            }
        }

        private void SerializeInteger(StreamBuffer dout, int serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.Integer);
            }
            lock (memInteger)
            {
                byte[] memInteger = GBitConverter.GetBytes(serObject);
                dout.Write(memInteger, 0, 4);
            }
        }

        private void SerializeIntegerArray(StreamBuffer dout, int[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.IntegerArray);
            }
            SerializeArrayLength(dout, serObject.Length);
            for (int i = 0; i < serObject.Length; i++)
            {
                SerializeInteger(dout, serObject[i], false);
            }
        }

        private void SerializeLong(StreamBuffer dout, long serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.Long);
            }
            lock (memLongBlockBytes)
            {
                memLongBlock[0] = serObject;
                byte[] data = GBitConverter.GetBytes(serObject);
                dout.Write(data, 0, 8);
            }
        }

        private void SerializeLongArray(StreamBuffer dout, long[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.LongArray);
            }
            SerializeArrayLength(dout, serObject.Length);
            for (int i = 0; i < serObject.Length; i++)
            {
                SerializeLong(dout, serObject[i], false);
            }
        }

        private void SerializeHashTable(StreamBuffer dout, Hashtable serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.Hashtable);
            }
            SerializeShort(dout, (short)serObject.Count, false);
            foreach (object obj2 in serObject.Keys)
            {
                Serialize(dout, obj2, true);
                Serialize(dout, serObject[obj2], true);
            }
        }

        private void SerializeHashTableArray(StreamBuffer dout, Hashtable[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.HashtableArray);
            }
            SerializeArrayLength(dout, serObject.Length);
            for (int i = 0; i < serObject.Length; i++)
            {
                SerializeHashTable(dout, serObject[i], false);
            }
        }

        private void SerializeOperationRequest(StreamBuffer stream, OperationRequest serObject, bool setType)
        {
            SerializeOperationRequest(stream, serObject.OperationCode, serObject.Parameters, setType);
        }

        private void SerializeParameterTable(StreamBuffer stream, Dictionary<byte, object> parameters)
        {
            if ((parameters == null) || (parameters.Count == 0))
            {
                SerializeShort(stream, 0, false);
            }
            else
            {
                SerializeShort(stream, (short) parameters.Count, false);
                foreach (KeyValuePair<byte, object> pair in parameters)
                {
                    stream.WriteByte(pair.Key);
                    Serialize(stream, pair.Value, true);
                }
            }
        }

        private void SerializeString(StreamBuffer stream, string value, bool setType)
        {
            if (setType)
            {
                stream.WriteByte((byte)GpType.String);
            }
            int byteCount = Encoding.UTF8.GetByteCount(value);
            if (byteCount > 0x7fff)
            {
                throw new NotSupportedException("Strings that exceed a UTF8-encoded byte-length of 32767 (short.MaxValue) are not supported. Yours is: " + byteCount.ToString());
            }
            SerializeShort(stream, (short) byteCount, false);
            int offset = 0;
            byte[] bufferAndAdvance = stream.GetBufferAndAdvance(byteCount, out offset);
            Encoding.UTF8.GetBytes(value, 0, value.Length, bufferAndAdvance, offset);
        }

        private void SerializeStringArray(StreamBuffer dout, string[] serObject, bool setType)
        {
            if (setType)
            {
                dout.WriteByte((byte)GpType.StringArray);
            }
            SerializeShort(dout, (short) serObject.Length, false);
            for (int i = 0; i < serObject.Length; i++)
            {
                SerializeString(dout, serObject[i], false);
            }
        }
            
    }
}

